#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include "led_opr.h"
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <asm/io.h>
#include <linux/sched.h>

/* 中断处理流程 */
/* 1. 初始化GPIO
 * 2. gpio=>irq，申请中断号
 * 3. request_irq 中断注册
 * 4. 执行中断函数
 */

/*① 确定主设备号，也可以让内核分配*/
static int major = 0;
static struct class *led_class;

/* 定义一个结构指针，用来获取具体单板的返回值,具体在初始化函数里面调用get_board_led_opr() */
struct led_operations *p_led_opr ;

/* 定义一个等待队列的标志 */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/* 中断事件标志, 中断服务程序置1 不进休眠，drv_read将它清0 进休眠*/
static volatile int ev_press = 0;
/* 按键中断的值 */
unsigned int key_val; //中断值

//引脚描述结构体
struct pin_irq_desc
{
    unsigned int irq;    //中断号
    unsigned int pin;    //中断标志寄存器，有中断产生时为1，无中断时为0:->0x40
    unsigned int number; //编号
    char *name;          //名称
};

struct pin_irq_desc pins_desc[] =
    {
        {76, 0x04, 0x01, "key_irq"},
};


/* 中断处理函数 */
static irqreturn_t gpio_key_irq(int irq, void *dev_id)
{
	key_val = p_led_opr->read();
	printk(KERN_INFO "key pressed\n");	
    ev_press = 1;                         //不进去休眠
    wake_up_interruptible(&button_waitq); //唤醒休眠的进程->跳到read函数把值返回给用户
	p_led_opr->write();//清除中断
	return IRQ_HANDLED;
}

static int button_dev_open (struct inode *node, struct file *file )
{
	int err;
	/* 初始化引脚操作 */
	p_led_opr->init();
		

	/* 申请中断号 */
	//Hi3519v101好几组GPIO只有一个中断号，所以这里得是共享中断IRQF_SHARED，作为共享中断，dev用来区分不同的中断
	err = request_irq(pins_desc->irq, gpio_key_irq, IRQF_SHARED, pins_desc->name, (void *)&pins_desc);
	if(err < 0)
	{
	  printk(KERN_ERR "cannot request irq\n");
	  return -EFAULT;
	}
	else
		printk("set key low irq mode\n");

	return 0;


}

static ssize_t button_dev_read (struct file *file, char __user *buf, size_t size, loff_t * offset)
{
		int err;
		
		/*如果没有按键动作, 休眠*/
		wait_event_interruptible(button_waitq, ev_press);
		
		err = copy_to_user(buf, &key_val, 1);//返回给用户的值
		ev_press = 0; //数据拷贝给运用之后，一次中断完成，进去休眠
		return 0;
}




static int button_dev_close (struct inode *node, struct file *file)
{
    free_irq(pins_desc->irq, (void *)&pins_desc);
	return 0;
}





/*② 定义自己的 file_operations 结构体*/
static  struct  file_operations led_drv={
	.owner     = THIS_MODULE,
	.read      = button_dev_read,
	.open      = button_dev_open,
	.release 	= button_dev_close,
	
};


/*④ 把 file_operations 结构体告诉内核： register_chrdev*/
/*⑤ 谁来注册驱动程序啊？得有一个入口函数：安装驱动程序时，就会去调用这个入口函数*/
static int __init led_init (void)
{
	
	int err;
	int i = 1;
	p_led_opr = get_board_led_opr();/* 操作寄存器的指针 */
	
	/* 注册、分配设备号 */
	major = register_chrdev(0, "my_led",&led_drv);//告诉内核结构体
	led_class = class_create(THIS_MODULE, "my_led");
	err = PTR_ERR(led_class);
	if (IS_ERR(led_class))
		return -1;
	device_create(led_class, NULL, MKDEV(major, i), NULL, "my_led");//注册驱动程序 dev/my_led0,1,...
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	return 0;
}


/*⑥ 有入口函数就应该有出口函数：卸载驱动程序时，出口函数调用 unregister_chrdev*/
static void __exit led_exit(void)
{
	int i = 1;
	
	//for(i = 0 ; i < p_led_opr->num ; i++)
	device_destroy(led_class, MKDEV(major, i));
	class_destroy(led_class);
	unregister_chrdev(major, "my_led");	
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

}


/*⑦ 其他完善：提供设备信息，自动创建设备节点： class_create, device_create*/
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");







